// WMS Library - http://wmslib.codeplex.com/
// Copyright (c) Tom Gaskins 2004 - Erik Putrycz 2013
// Licensed under the Microsoft Public License (Ms-PL)

using System.Linq;
using System.Collections.Generic;
using System.Xml.Linq;
using System;
using System.Diagnostics;
using System.Xml;
using System.Text.RegularExpressions;

namespace WMSLib.Client
{

    public enum ImageType
    {
        ImagePng, ImageJpg
        
    }
    /// <summary>
    /// Represents a layer within a WMS capabilities description.
    /// See Wms.Client.Capabilities and Wms.Client.WMSDialog for
    /// example usage.
    /// </summary>
    [DebuggerDisplay("Title={Title}")]
    public class Layer
    {
        private Server _server; // The WMS server object representing the server holding this layer.
        private XElement _layer;
        private Layer _parent;


        internal Layer(XElement layer, Server server)
        {
            _layer = layer;
            _parent = null;
            this._server = server;
        }

        public Server Server
        {
            get { return this._server; }
        }

        // The following are utilities for Layers.

        public Layer FindLayerByName(string name)
        {
            if (this.Name == name)
                return this;
            foreach (var item in Layers)
            {
                var l = item.FindLayerByName(name);
                if (l != null)
                    return l;
            }
            return null;
        }

        public List<Layer> FindBy(Func<Layer, bool> criteria, double lat1, double lon1, double lat2, double lon2)
        {
            var l = new List<Layer>();
            FindBy(l, criteria, lat1, lon1, lat2, lon2);
            if (l.Count == 0)
                return null;
            return l;
        }

        public void FindBy(List<Layer> results, Func<Layer, bool> criteria, double lat1, double lon1, double lat2, double lon2)
        {
            if (this.BoundingBoxes != null && criteria(this))
            {
                var bb = LatLonBoundingBox;
                if (bb != null)
                {
                    if (bb.MinX <= lon1 && lon2 <= bb.MaxX && bb.MinY <= lat1 && lat2 <= bb.MaxY)
                    {
                        results.Add(this);
                        return;
                    }
                }
            }
            if (this.Layers != null)
            {
                foreach (var item in Layers)
                {
                    item.FindBy(results, criteria, lat1, lon1, lat2, lon2);
                }
            }
        }

        public Layer ParentLayer
        {
            get
            { return _parent; }

        }

        internal static List<Layer> GetLayers(XElement doc, Server server)
        {
            // The incoming parentNode might not be a Layer node, but we assume it is here in
            // order to re-use the Layers property of the Layer class. If parentNode has
            // no Layer children, then the Layers property returns an empty array.
            Layer layerParent = new Layer(doc, server);
            return layerParent.Layers;
        }

        public class UriAndFormatType // This is an often-used data type in the WMS schema.
        {
            public string Uri;
            public string Format;

            internal UriAndFormatType()
            {
                this.Format = string.Empty;
                this.Uri = string.Empty;
            }

            public override bool Equals(System.Object obj)
            {
                if (obj == null)
                    return false;

                if (this == obj)
                    return true;

                if (obj.GetType() != this.GetType())
                    return false;

                UriAndFormatType t = (UriAndFormatType)obj;
                return this.Uri.Equals(t.Uri) && this.Format.Equals(t.Format);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }

            public bool IsEmpty
            {
                get { return this.Uri.Equals(string.Empty); }
            }
        }

        // All the following use XPath search strings to find various bits of information in the
        // server's WMS capabilities description.

        //internal static UriAndFormatType[] GetUriAndFormatInstances(System.Xml.XPath.XPathNavigator node, string pattern)
        //{
        //    System.Xml.XPath.XPathNodeIterator iter = node.Select(Capabilities.ExpandPattern(pattern));
        //    UriAndFormatType[] retVal = new UriAndFormatType[iter.Count];
        //    while (iter.MoveNext())
        //    {
        //        int i = iter.CurrentPosition - 1;
        //        retVal[i] = new UriAndFormatType();
        //        retVal[i].Format = Capabilities.GetStringInstance(iter.Current, @"./Format");
        //        retVal[i].Uri = Capabilities.GetOnlineResourceInstance(iter.Current, @"./OnlineResource");
        //    }
        //    return retVal;
        //}

        //
        // The following are non-inherited properties.
        //

        public string Name
        {
            get { return ReadStringOrNull(_layer, "Name"); }
        }

        public static string ReadStringOrNull(XElement el, string name)
        {
            var res = el.Elements(name).FirstOrDefault();
            if (res == null)
                return null;
            return res.Value;
        }

        public string Title
        {
            get { return ReadStringOrNull(_layer, "Title"); }
        }

        public string Abstract
        {
            get
            {
                var res = ReadStringOrNull(_layer, "Abstract");
                if (string.IsNullOrEmpty(res) && _parent != null)
                    return _parent.Abstract;
                return res;
            }
        }

        //public string[] KeywordList
        //{
        //    get {return Capabilities.GetStringsInstance(this.nav, @"./KeywordList/Keyword");}
        //}

        //public class IdentifierType
        //{
        //    public string Identifier;
        //    public string Authority;

        //    internal IdentifierType()
        //    {
        //        this.Identifier = string.Empty;
        //        this.Authority = string.Empty;
        //    }
        //}

        //public IdentifierType[] Identifiers
        //{
        //    get
        //    {
        //        var identifiers = _layer.Elements("Identifier");
        //        System.Xml.XPath.XPathNodeIterator iter = this.nav.Select(Capabilities.ExpandPattern(@"./Identifier"));
        //        IdentifierType[] retVal = new IdentifierType[iter.Count];
        //        while (iter.MoveNext())
        //        {
        //            int i = iter.CurrentPosition - 1;
        //            retVal[i] = new IdentifierType();
        //            retVal[i].Identifier = iter.Current.Value;
        //            retVal[i].Authority = Capabilities.GetStringInstance(iter.Current, @"./@Authority");
        //        }
        //        return retVal;
        //    }
        //}

        //public UriAndFormatType[] DataUris
        //{
        //    get {return GetUriAndFormatInstances(this.nav, @"./DataUrl");}
        //}

        //public UriAndFormatType[] FeatureListUris
        //{
        //    get {return GetUriAndFormatInstances(this.nav, @"./FeatureListUrl");}
        //}

        public class MetadataUriType
        {
            public UriAndFormatType MetadataUri;
            public string Type;

            internal MetadataUriType()
            {
                this.Type = string.Empty;
                this.MetadataUri = new UriAndFormatType();
            }
        }

        public List<MetadataUriType> MetadataUris
        {
            get
            {
                var results = new List<MetadataUriType>();
                foreach (var item in _layer.Elements("MetadataUrl"))
                {
                    var mut = new MetadataUriType();
                    var onlineResource = item.Elements("OnlineResource").First();
                    mut.Type = getAttribute(item, "Type");
                    mut.MetadataUri = new UriAndFormatType();
                    mut.MetadataUri.Uri = getAttribute(onlineResource, "xlink:href");
                    mut.MetadataUri.Format = getAttribute(onlineResource, "xlink:type");
                    results.Add(mut);
                }

                return results;
            }
        }

        public List<Layer> Layers
        {
            get
            {
                var childLayers = new List<Layer>();
                foreach (var item in _layer.Elements("Layer"))
                {
                    var child = new Layer(item, _server);
                    child._parent = this;
                    childLayers.Add(child);
                }
                return childLayers;
            }
        }

        //
        // The following are properties that have "Replace" inheritance.
        //

        public class BoundingBoxType
        {
            public string Srs;
            public double MinX;
            public double MinY;
            public double MaxX;
            public double MaxY;
            public double ResX;
            public double ResY;


            //-74.6346271171326,46.22741707027332,-72.87694133662491,47.26928933489761
            public string ToQuery()
            {
                return string.Format("{0},{1},{2},{3}", MinX, MinY, MaxX, MaxY);
            }

            public override string ToString()
            {
                string retVal = this.MinY + " to " + this.MaxY + " Latitude, " + this.MinX + " to " + this.MaxX + " Longitude";
                if (this.Srs != null && !this.Srs.Equals(string.Empty))
                {
                    retVal += ", SRS: " + this.Srs;
                }
                if (this.ResX != 0 || this.ResY != 0)
                {
                    retVal += ", Resolution = (" + this.ResX.ToString() + ", " + this.ResY.ToString() + ")";
                }
                return retVal;
            }

        }

        public BoundingBoxType LatLonBoundingBox
        {
            get
            {
                var bbBox = _layer.Elements("LatLonBoundingBox").FirstOrDefault();
                if (bbBox != null)
                {
                    var retVal = new BoundingBoxType();
                    retVal.MinX = double.Parse(getAttribute(bbBox, "minx"));
                    retVal.MinY = double.Parse(getAttribute(bbBox, "miny"));
                    retVal.MaxX = double.Parse(getAttribute(bbBox, "maxx"));
                    retVal.MaxY = double.Parse(getAttribute(bbBox, "maxy"));
                    return retVal;
                }
                else
                {
                    // See whether one is defined higher up the Layer hierarchy.
                    if (this.ParentLayer != null)
                    {
                        return ParentLayer.LatLonBoundingBox;
                    }
                }
                return null;
            }
        }

        public List<BoundingBoxType> BoundingBoxes
        {
            get
            {
                var bboxes = new List<BoundingBoxType>();
                foreach (var bbBox in _layer.Elements("BoundingBox"))
                {
                    var retVal = new BoundingBoxType();
                    retVal.Srs = getAttribute(bbBox, "SRS");
                    retVal.MinX = double.Parse(getAttribute(bbBox, "minx"));
                    retVal.MinY = double.Parse(getAttribute(bbBox, "miny"));
                    retVal.MaxX = double.Parse(getAttribute(bbBox, "maxx"));
                    retVal.MaxY = double.Parse(getAttribute(bbBox, "maxy"));
                    string resX = getAttribute(bbBox, "resx");
                    if (!string.IsNullOrEmpty(resX))
                    {
                        retVal.ResX = double.Parse(resX);
                    }
                    string resY = getAttribute(bbBox, "resy");
                    if (!string.IsNullOrEmpty(resY))
                    {
                        retVal.ResY = double.Parse(resY);
                    }
                    bboxes.Add(retVal);
                }
                if (bboxes.Count == 0 && ParentLayer != null)
                {
                    // See whether one is defined higher up the Layer hierarchy.
                    return ParentLayer.BoundingBoxes;
                }
                return bboxes;
            }
        }

        public class ExtentType
        {
            public string Name;
            public string Default;
            public string Extent;

            public ExtentType()
            {
                this.Name = string.Empty;
                this.Default = string.Empty;
                this.Extent = string.Empty;
            }

            public bool IsEmpty
            {
                get
                {
                    return string.IsNullOrEmpty(this.Name) && string.IsNullOrEmpty(this.Default) && string.IsNullOrEmpty(this.Extent);
                }
            }

            public override string ToString()
            {
                string retVal = "";
                if (!this.Name.Equals(string.Empty))
                {
                    retVal += this.Name;
                }
                if (!this.Extent.Equals(string.Empty))
                {
                    retVal += ", " + this.Extent;
                }
                if (!this.Default.Equals(string.Empty))
                {
                    retVal += ", Default = " + this.Default;
                }
                return retVal;
            }
        }

        public List<ExtentType> Extents
        {
            get
            {
                var extends = new List<ExtentType>();
                foreach (var item in _layer.Elements("Extent"))
                {
                    var ext = new ExtentType() { Extent = item.Value, Default = getAttribute(item, "default"), Name = getAttribute(item, "name") };
                    extends.Add(ext);
                }
                if (extends.Count == 0 && ParentLayer != null)
                    return ParentLayer.Extents;
                return extends;
            }
        }

        public class ScaleHintType
        {
            // The WMS 1.1.1 spec doesn't demand that these values be numbers. Therefore 
            // treat them as strings. The application can easily parse them as numbers if
            // appropriate. (Using double.Parse())
            public string Min;
            public string Max;

            public ScaleHintType()
            {
                this.Min = string.Empty;
                this.Max = string.Empty;
            }

            public bool IsEmpty
            {
                get
                {
                    return this.Min.Equals(string.Empty) && this.Max.Equals(string.Empty);
                }
            }
        }

        public ScaleHintType ScaleHint
        {
            get
            {
                var scaleHint = _layer.Elements("ScaleHint").FirstOrDefault();
                if (scaleHint == null)
                    if (ParentLayer != null)
                        return ParentLayer.ScaleHint;
                    else
                        return null;

                var retVal = new ScaleHintType();
                retVal.Min = getAttribute(scaleHint, "Min");
                retVal.Max = getAttribute(scaleHint, "Max");
                return retVal;
            }
        }

        public class LogoOrLegendUriType
        {
            public UriAndFormatType Uri;
            public double Width;
            public double Height;

            internal LogoOrLegendUriType()
            {
                this.Uri = new UriAndFormatType();
            }

            public bool IsEmpty
            {
                get { return Uri == null || this.Uri.IsEmpty; }
            }
        }

        public class AttributionType
        {
            public string Title;
            public string Uri;
            public LogoOrLegendUriType LogoUri;

            internal AttributionType()
            {
                this.Title = string.Empty;
                this.Uri = string.Empty;
                this.LogoUri = new LogoOrLegendUriType();
            }

            public bool IsEmpty
            {
                get
                {
                    return this.Title.Equals(string.Empty)
                        && this.Uri.Equals(string.Empty)
                        && this.LogoUri.IsEmpty;
                }
            }
        }

        public static string ReadOnlineResourceHRef(XElement el)
        {
            if (el == null)
                return null;
            return getAttribute(el, @"{http://www.w3.org/1999/xlink}href");
        }


        //<Attribution>
        //    <Title>Meteorological Service of Environment Canada</Title>
        //    <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://www.weatheroffice.gc.ca/mainmenu/contact_us_e.html"/>
        //    <LogoURL width="228" height="22">
        //        <Format>image/png</Format>
        //        <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://wms.weatheroffice.gc.ca/geomet//?SERVICE=WMS%26VERSION=1.1.0%26REQUEST=GetLegendGraphic%26STYLE=LOGO_SMC%26format=image/png"/>
        //    </LogoURL>
        //</Attribution>
        public AttributionType Attribution
        {
            get
            {
                var attribution = _layer.Elements("Attribution").FirstOrDefault();
                if (attribution == null)
                    if (ParentLayer != null)
                        return ParentLayer.Attribution;
                    else
                    {
                        if (Server.AttributionURI != null)
                            return new AttributionType() { LogoUri = new LogoOrLegendUriType() { Uri = new UriAndFormatType() { Uri = Server.AttributionURI } } };
                        return null;
                    }

                var retVal = new AttributionType();
                retVal.Title = attribution.Elements("Title").First().Value;
                retVal.Uri = ReadOnlineResourceHRef(attribution.Elements("OnlineResource").FirstOrDefault());
                retVal.LogoUri = ReadLogoOrLegendUri(attribution.Elements("LogoURL").FirstOrDefault());
                return retVal;
            }
        }

        public static LogoOrLegendUriType ReadLogoOrLegendUri(XElement xElement)
        {
            if (xElement == null)
                return null;
            var logo = new LogoOrLegendUriType();
            logo.Height = double.Parse(getAttribute(xElement, "height"));
            logo.Width = double.Parse(getAttribute(xElement, "width"));
            logo.Uri = ReadUriAndFormat(xElement);
            return logo;
        }

        public static UriAndFormatType ReadUriAndFormat(XElement xElement)
        {
            if (xElement == null)
                return null;
            var uri = new UriAndFormatType();
            uri.Format = xElement.Elements("Format").First().Value;
            uri.Uri = ReadOnlineResourceHRef(xElement.Elements("OnlineResource").FirstOrDefault());
            return uri;
        }

        public bool Queryable
        {
            get
            {
                var att = getAttribute(_layer, "queryable");
                if (att == null)
                    if (ParentLayer == null)
                    {
                        return false;
                    }
                    else
                    {
                        return ParentLayer.Queryable;
                    }
                return Boolean.Parse(att);
            }
        }

        private static string getAttribute(XElement el, string att)
        {
            var at = el.Attribute(att);
            if (at == null)
                return null;
            return at.Value;
        }

        public bool Opaque
        {
            get
            {
                var att = getAttribute(_layer, "opaque");
                if (att == null)
                    if (ParentLayer == null)
                    {
                        return false;
                    }
                    else
                    {
                        return ParentLayer.Opaque;
                    }
                return Boolean.Parse(att);
            }
        }

        public bool NoSubsets
        {
            get
            {
                var att = getAttribute(_layer, "noSubsets");
                if (att == null)
                    if (ParentLayer == null)
                    {
                        return false;
                    }
                    else
                    {
                        return ParentLayer.NoSubsets;
                    }

                return Boolean.Parse(att);
            }
        }

        public int Cascaded
        {
            get
            {
                var att = getAttribute(_layer, "Cascaded");
                if (att == null)
                    if (ParentLayer == null)
                    {
                        return 0;
                    }
                    else
                    {
                        return ParentLayer.Cascaded;
                    }
                return int.Parse(att);
            }
        }

        public int FixedWidth
        {
            get
            {
                var att = getAttribute(_layer, "fixedWidth");
                if (att == null)
                    if (ParentLayer == null)
                    {
                        return 0;
                    }
                    else
                    {
                        return ParentLayer.FixedWidth;
                    }
                return int.Parse(att);
            }
        }

        public int FixedHeight
        {
            get
            {
                var att = getAttribute(_layer, "fixedHeight");
                if (att == null)
                    if (ParentLayer == null)
                    {
                        return 0;
                    }
                    else
                    {
                        return ParentLayer.FixedHeight;
                    }
                return int.Parse(att);
            }
        }

        //
        // The following properties have "Add" inheritance.
        //

        public class DimensionType
        {
            public string Name;
            public string Units;
            public string UnitSymbol;

            public DimensionType()
            {
                this.Name = string.Empty;
                this.Units = string.Empty;
                this.UnitSymbol = string.Empty;
            }

            public override bool Equals(System.Object obj)
            {
                if (obj == null)
                    return false;

                if (this == obj)
                    return true;

                if (obj.GetType() != this.GetType())
                    return false;

                DimensionType d = (DimensionType)obj;
                return this.Name.Equals(d.Name)
                    && this.Units.Equals(d.Units)
                    && this.UnitSymbol.Equals(d.UnitSymbol);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }

            public override string ToString()
            {
                string retVal = this.Name + ", " + this.Units;
                if (!this.UnitSymbol.Equals(string.Empty))
                {
                    retVal += " (" + this.UnitSymbol + ")";
                }
                return retVal;
            }

        }

        public List<DimensionType> Dimensions
        {
            get
            {
                // Collect any values from ancestors.
                List<DimensionType> retVal = new List<DimensionType>();
                if (this.ParentLayer != null)
                {
                    var parentValues = this.ParentLayer.Dimensions;
                    if (parentValues.Count > 0)
                    {
                        retVal.AddRange(parentValues);
                    }
                }

                // Add those uniquely defined at this Layer.                
                foreach (var item in _layer.Elements("Dimension"))
                {
                    var dim = new DimensionType();
                    dim.Name = getAttribute(item, "Name");
                    dim.Units = getAttribute(item, "Units");
                    dim.UnitSymbol = getAttribute(item, "UnitSymbol");
                    if (!retVal.Contains(dim))
                    {
                        retVal.Add(dim);
                    }

                }
                return retVal;
            }
        }

        public List<string> Srs
        {
            get
            {
                // Collect any values from ancestors.
                List<string> retVal = new List<string>();

                // Add those uniquely defined at this Layer.
                foreach (var item in _layer.Elements("SRS"))
                {
                    var srs = item.Value;
                    if (!retVal.Contains(srs))
                    {
                        retVal.Add(srs);
                    }
                }

                if (this.ParentLayer != null && retVal.Count == 0)
                {
                    var parentValues = this.ParentLayer.Srs;
                    if (parentValues.Count > 0)
                    {
                        retVal.AddRange(parentValues);
                    }
                }
                return retVal;
            }
        }

        public class AuthorityUriType
        {
            public string Name;
            public string Uri;

            internal AuthorityUriType()
            {
                this.Name = string.Empty;
                this.Uri = string.Empty;
            }

            public override bool Equals(System.Object obj)
            {
                if (obj == null)
                    return false;

                if (this == obj)
                    return true;

                if (obj.GetType() != this.GetType())
                    return false;

                return this.Name.Equals(((AuthorityUriType)obj).Name)
                    && this.Uri.Equals(((AuthorityUriType)obj).Uri);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }

        //<AuthorityURL name="Root auth 1">
        //  <OnlineResource xlink:href="http://geoserver.org"/>
        //</AuthorityURL>
        //<AuthorityURL name="Root auth 2">
        //  <OnlineResource xlink:href="http://some.authority.org"/>
        //</AuthorityURL>
        public List<AuthorityUriType> AuthorityUris
        {
            get
            {
                // Collect any values from ancestors.
                var retVal = new List<AuthorityUriType>();
                if (this.ParentLayer != null)
                {
                    var parentValues = this.ParentLayer.AuthorityUris;
                    if (parentValues.Count > 0)
                    {
                        retVal.AddRange(parentValues);
                    }
                }
                foreach (var item in _layer.Elements("AuthorityURL"))
                {
                    var auth = new AuthorityUriType();
                    auth.Name = getAttribute(item, "Name");
                    auth.Uri = ReadOnlineResourceHRef(item);
                    if (!retVal.Contains(auth))
                    {
                        retVal.Add(auth);
                    }
                }
                return retVal;
            }
        }

        public class WMSStyle
        {
            public string Name;
            public string Title;
            public string Abstract;
            public UriAndFormatType StyleUri;
            public UriAndFormatType StyleSheetUri;
            public LogoOrLegendUriType LegendUri;

            internal WMSStyle()
            {
                this.Name = string.Empty;
                this.Title = string.Empty;
                this.Abstract = string.Empty;
                this.StyleUri = new UriAndFormatType();
                this.StyleSheetUri = new UriAndFormatType();
                this.LegendUri = new LogoOrLegendUriType();
            }

            public override bool Equals(System.Object obj)
            {
                if (obj == null)
                    return false;

                if (this == obj)
                    return true;

                if (obj.GetType() != this.GetType())
                    return false;

                WMSStyle s = (WMSStyle)obj;
                return this.Name.Equals(s.Name)
                    && this.Title.Equals(s.Title)
                    && this.Abstract.Equals(s.Abstract)
                    && this.StyleUri.Equals(s.StyleUri)
                    && this.StyleSheetUri.Equals(s.StyleSheetUri)
                    && this.LegendUri.Equals(s.LegendUri);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }

        //<Style>
        //    <Name>PRECIPMM</Name>
        //    <Title>Precipitations in millimeters</Title>
        //    <LegendURL width="96" height="270">
        //        <Format>image/png</Format>
        //        <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://wms.weatheroffice.gc.ca/geomet//?SERVICE=WMS%26VERSION=1.1.1%26REQUEST=GetLegendGraphic%26STYLE=PRECIPMM%26LAYER=CAPA.6F_PR%26format=image/png"/>
        //    </LegendURL>
        //</Style>
        public List<WMSStyle> Styles
        {
            get
            {
                // Collect any values from ancestors.
                var retVal = new List<WMSStyle>();
                if (this.ParentLayer != null)
                {
                    var parentValues = this.ParentLayer.Styles;
                    if (parentValues.Count > 0)
                    {
                        retVal.AddRange(parentValues);
                    }
                }

                // Add those uniquely defined at this Layer.
                foreach (var item in _layer.Elements("Style"))
                {
                    var style = new WMSStyle();
                    style.Name = ReadStringOrNull(item, "Name");
                    style.Title = ReadStringOrNull(item, "Title");
                    style.Abstract = ReadStringOrNull(item, "Abstract");
                    style.StyleUri = ReadUriAndFormat(item.Elements("StyleUrl").FirstOrDefault());
                    style.StyleSheetUri = ReadUriAndFormat(item.Elements("StyleSheetUrl").FirstOrDefault());
                    style.LegendUri = ReadLogoOrLegendUri(item.Elements("LegendURL").FirstOrDefault());
                    if (!retVal.Contains(style))
                    {
                        retVal.Add(style);
                    }
                }
                return retVal;
            }
        }

        private static readonly Regex RegExInterval = new Regex(@"^P(?:\d+Y|Y)?(?:\d+M|M)?(?:\d+D|D)?(?<T>T(?:(?<H>\d+)H|H)?(?:(?<M>\d+)M|M)?(?:\d+(?:\.\d{1,2})?S|S)?)?$");

        public ExtentType GetExtentValue(string extend)
        {
            if (this.Extents == null)
                return null;
            var ext = this.Extents.FirstOrDefault(e => e.Name.ToLower() == extend.ToLower());
            if (ext == null)
                return null;
            return ext;
        }

        public Tuple<DateTime, DateTime, TimeSpan> ParseISOInterval()
        {
            var timeData = GetExtentValue("time");
            if (timeData == null)
                return null;
            var elements = timeData.Extent.Split(new char[] { '/' }).ToList();
            var d1 = XmlConvert.ToDateTimeOffset(elements[0]);
            var d2 = XmlConvert.ToDateTimeOffset(elements[1]);
            var hId = RegExInterval.GroupNumberFromName("H");
            var mId = RegExInterval.GroupNumberFromName("M");
            var m = RegExInterval.Match(elements[2]);
            TimeSpan ts = TimeSpan.Zero;
            if (m.Groups[hId].Success)
            {
                ts = ts.Add(TimeSpan.FromHours(Int32.Parse(m.Groups[hId].Value)));
            }
            if (m.Groups[mId].Success)
            {
                ts = ts.Add(TimeSpan.FromMinutes(Int32.Parse(m.Groups[mId].Value)));
            }
            var tp = Tuple.Create(d1.LocalDateTime, d2.LocalDateTime, ts);
            return tp;
        }

        public List<Tuple<DateTime, Uri>> BuildAllURLs(int w, int h, Layer.BoundingBoxType bb,ImageType type)
        {
            var elevation = GetExtentValue("elevation");
            var tp = ParseISOInterval();
            var intervalCount = tp == null ? 0 : (tp.Item2 - tp.Item1).TotalMinutes / tp.Item3.TotalMinutes;
            var uris = new List<Tuple<DateTime, Uri>>();
            for (int i = 0; i < (intervalCount + 1); i++)
            {
                var tmStr = "";
                DateTime cDt = DateTime.Now;
                if (tp != null)
                {
                    cDt = tp.Item1.AddMinutes(tp.Item3.TotalMinutes * i);
                    var dt = new DateTimeOffset(cDt);
                    var dtXML = XmlConvert.ToString(dt.ToUniversalTime());
                    tmStr = "&TIME=" + dtXML;
                }
                var style = "";
                if (this.Styles.Count > 0)
                    style = "&STYLES=" + this.Styles.First().Name;
                var elStr = elevation == null ? "" : "&ELEVATION=" + elevation.Default;
                var srs = "&SRS=" + Srs.First();

                var imgType = "image/jpeg";//image/png
                if (type == ImageType.ImagePng)
                    imgType = "image/png";
                var urlq = String.Format(@"LANG=E&Service=WMS&Version=1.1.1&Request=GetMap&layers={0}&width={1}&Height={2}&Transparent=TRUE{3}{4}{5}{6}&format={7}&BBOX={8}", this.Name, w, h, elStr, tmStr, style, srs, imgType, bb.ToQuery());
                var url = new Uri(_server.ServerRootURL + urlq);
                uris.Add(Tuple.Create(cDt, url));
            }
            return uris;
        }
    }
}